#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
自动删除指定目录下过期CSV文件的程序
支持通过JSON配置文件自定义执行时间和目标目录
"""

import os
import fnmatch
import glob
import json
import schedule
import time
from datetime import datetime, timedelta
import logging
import sys
from typing import Dict, Any, Optional

class ConfigManager:
    """配置文件管理器"""
    
    def __init__(self, config_path: str = "config.json"):
        self.config_path = config_path
        self.config = self.load_config()
    
    def load_config(self) -> Dict[str, Any]:
        """加载配置文件"""
        try:
            if not os.path.exists(self.config_path):
                print(f"警告: 配置文件 {self.config_path} 不存在，使用默认配置")
                return self.get_default_config()
            
            with open(self.config_path, 'r', encoding='utf-8') as f:
                config = json.load(f)
            
            # 验证和合并配置
            return self.validate_and_merge_config(config)
            
        except json.JSONDecodeError as e:
            print(f"错误: 配置文件格式无效 - {e}")
            print("使用默认配置")
            return self.get_default_config()
        except Exception as e:
            print(f"错误: 加载配置文件失败 - {e}")
            print("使用默认配置")
            return self.get_default_config()
    
    def get_default_config(self) -> Dict[str, Any]:
        """获取默认配置"""
        return {
            "settings": {
                "description": "自动删除老CSV文件配置",
                "version": "1.0"
            },
            "target_directory": "./data",
            "execution_time": "00:10:00",
            "file_pattern": "*.csv",
            "retention_days": 2,
            "log_enabled": True,
            "log_file": "./delete_operation.log",
            "dry_run": False,
            "immediate_execution": True
        }
    
    def validate_and_merge_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
        """验证并合并配置"""
        default_config = self.get_default_config()
        
        # 确保settings部分存在
        if "settings" not in config:
            config["settings"] = default_config["settings"]
        else:
            config["settings"].update(default_config["settings"])
        
        # 合并其他配置项，使用默认值
        for key, default_value in default_config.items():
            if key != "settings" and key not in config:
                config[key] = default_value
        
        # 验证和清理配置
        return self.clean_config(config)
    
    def clean_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
        """清理和验证配置参数"""
        # 清理目标目录路径
        target_dir = config.get("target_directory", "./data")
        # 转换为绝对路径
        if not os.path.isabs(target_dir):
            target_dir = os.path.abspath(target_dir)
        config["target_directory"] = target_dir
        
        # 验证执行时间格式 (HH:MM)
        execution_time = config.get("execution_time", "00:10:00")
        if not self.validate_time_format(execution_time):
            print(f"警告: 无效的执行时间格式 {execution_time}，使用默认时间 00:10:00")
            config["execution_time"] = "00:10:00"
        
        # 验证保留天数
        retention_days = config.get("retention_days", 2)
        if not isinstance(retention_days, int) or retention_days < 0:
            print(f"警告: 无效的保留天数 {retention_days}，使用默认天数 2")
            config["retention_days"] = 2
        
        # 验证文件模式
        file_pattern = config.get("file_pattern", "*.csv")
        if not file_pattern.startswith("*."):
            print(f"警告: 文件模式 {file_pattern} 可能无效，建议使用 *.csv 格式")
        
        return config
    
    def validate_time_format(self, time_str: str) -> bool:
        """验证时间格式 HH:MM"""
        try:
            parts = time_str.split(":")
            if len(parts) != 2:
                return False
            hours, minutes = int(parts[0]), int(parts[1])
            return 0 <= hours <= 23 and 0 <= minutes <= 59
        except ValueError:
            return False
    
    def get_config(self) -> Dict[str, Any]:
        """获取当前配置"""
        return self.config.copy()
    
    def save_config(self) -> bool:
        """保存当前配置到文件"""
        try:
            with open(self.config_path, 'w', encoding='utf-8') as f:
                json.dump(self.config, f, indent=2, ensure_ascii=False)
            print(f"配置已保存到: {self.config_path}")
            return True
        except Exception as e:
            print(f"保存配置失败: {e}")
            return False

# 配置日志
def setup_logging(config: Dict[str, Any]):
    """设置日志配置"""
    log_enabled = config.get("log_enabled", True)
    log_file = config.get("log_file", "./delete_operation.log")
    
    if not log_enabled:
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[logging.StreamHandler()]
        )
        return
    
    # 确保日志目录存在
    log_dir = os.path.dirname(log_file)
    if log_dir and not os.path.exists(log_dir):
        os.makedirs(log_dir)
    
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler(log_file, encoding='utf-8'),
            logging.StreamHandler()
        ]
    )

class OldFileDeleter:
    def __init__(self, config: Dict[str, Any]):
        """
        初始化文件删除器
        
        Args:
            config (Dict[str, Any]): 配置字典
        """
        self.config = config
        self.target_directory = config["target_directory"]
        self.file_pattern = config["file_pattern"]
        self.retention_days = config["retention_days"]
        self.dry_run = config.get("dry_run", False)
        self.logger = logging.getLogger(__name__)
        
        self.logger.info(f"文件删除器已初始化")
        self.logger.info(f"目标目录: {self.target_directory}")
        self.logger.info(f"文件模式: {self.file_pattern}")
        self.logger.info(f"保留天数: {self.retention_days} 天")
        if self.dry_run:
            self.logger.info("运行模式: 模拟运行（不会实际删除文件）")
        
    def parse_filename_date(self, filename):
        """
        从文件名中解析日期（文件名格式：YYYYMMDDHHMMSS.csv）
        
        Args:
            filename (str): 文件名
            
        Returns:
            datetime: 解析出的日期时间，如果解析失败返回None
        """
        try:
            # 移除路径和扩展名
            basename = os.path.basename(filename)
            if not basename.endswith('.csv'):
                return None
                
            # 提取日期时间部分（前14位：YYYYMMDDHHMMSS）
            date_str = basename[:-4]  # 去掉.csv
            if len(date_str) >= 14:
                year = int(date_str[:4])
                month = int(date_str[4:6])
                day = int(date_str[6:8])
                hour = int(date_str[8:10])
                minute = int(date_str[10:12])
                second = int(date_str[12:14])
                
                return datetime(year, month, day, hour, minute, second)
        except (ValueError, IndexError) as e:
            self.logger.warning(f"无法解析文件名 {filename} 的日期: {e}")
            
        return None
    
    def get_cutoff_date(self):
        """
        获取删除文件的截止日期（删除指定天数前及更早的文件）
        
        Returns:
            datetime: 截止日期
        """
        # 获取今天0点
        today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
        # 截止日期是配置的保留天数前
        cutoff_date = today - timedelta(days=self.retention_days)
        return cutoff_date
    
    def delete_old_files(self):
        """
        删除指定目录下符合条件的老文件
        """
        try:
            if not os.path.exists(self.target_directory):
                self.logger.error(f"目标目录不存在: {self.target_directory}")
                return
            
            # 获取截止日期
            cutoff_date = self.get_cutoff_date()
            retention_date = cutoff_date + timedelta(days=1)  # 保留的最后一天
            
            self.logger.info(f"开始检查目录: {self.target_directory}")
            self.logger.info(f"删除截止日期: {cutoff_date.strftime('%Y-%m-%d %H:%M:%S')} (将删除此日期之前的文件)")
            self.logger.info(f"保留日期范围: {retention_date.strftime('%Y-%m-%d')} 至今天")
            
            # 查找所有匹配的文件（递归搜索）
            files_to_delete = []
            valid_files = []

            # 使用os.walk进行递归搜索
            for root, dirs, files in os.walk(self.target_directory):
                for file in files:
                    # 使用fnmatch检查文件模式匹配
                    if fnmatch.fnmatch(file, self.file_pattern):
                        file_path = os.path.join(root, file)
                        basename = os.path.basename(file_path)
                        file_date = self.parse_filename_date(file_path)
                        
                        if file_date is None:
                            self.logger.warning(f"无法解析文件日期，跳过: {file_path}")
                            continue
                        
                        valid_files.append((file_path, file_date, basename))
                
                        # 如果文件日期早于截止日期，加入删除列表
                        if file_date < cutoff_date:
                            files_to_delete.append((file_path, file_date, basename))
            
            if not valid_files:
                self.logger.info("未找到有效的文件")
                return
            
            # 显示文件统计信息
            self.logger.info(f"找到 {len(valid_files)} 个有效文件")
            self.logger.info(f"需要删除 {len(files_to_delete)} 个过期文件")
            
            # 显示要删除的文件列表
            if files_to_delete:
                self.logger.info("准备删除的文件:")
                total_size = 0
                for file_path, file_date, basename in files_to_delete:
                    file_size = os.path.getsize(file_path)
                    total_size += file_size
                    self.logger.info(f"  - {basename} ({file_date.strftime('%Y-%m-%d %H:%M:%S')}) - {file_size} bytes")
                
                self.logger.info(f"将释放磁盘空间: {total_size} bytes ({total_size/1024:.2f} KB)")
                
                if self.dry_run:
                    self.logger.info("=== 模拟运行模式 - 不会实际删除文件 ===")
            else:
                self.logger.info("没有需要删除的文件")
            
            # 执行删除操作
            if not self.dry_run:
                deleted_count = 0
                failed_count = 0
                
                for file_path, file_date, basename in files_to_delete:
                    try:
                        os.remove(file_path)
                        self.logger.info(f"已删除: {basename}")
                        deleted_count += 1
                    except OSError as e:
                        self.logger.error(f"删除文件失败 {basename}: {e}")
                        failed_count += 1
                
                # 总结
                self.logger.info(f"删除操作完成: 成功 {deleted_count} 个，失败 {failed_count} 个")
            else:
                self.logger.info(f"模拟删除操作完成: 本应删除 {len(files_to_delete)} 个文件")
            
        except Exception as e:
            self.logger.error(f"执行删除操作时发生错误: {e}")
    
    def run_scheduler(self):
        """
        启动定时任务
        """
        execution_time = self.config.get("execution_time", "00:10:00")
        immediate_execution = self.config.get("immediate_execution", True)
        
        self.logger.info("启动文件删除定时任务")
        self.logger.info(f"定时任务: 每天 {execution_time} 执行")
        
        # 设置定时任务
        try:
            schedule.every().day.at(execution_time).do(self.delete_old_files)
            self.logger.info(f"定时任务已设置: 每天 {execution_time}")
        except Exception as e:
            self.logger.error(f"设置定时任务失败: {e}")
            return
        
        # 立即执行一次（可选）
        if immediate_execution:
            self.logger.info("立即执行一次文件检查和删除操作")
            self.delete_old_files()
        
        # 保持程序运行
        self.logger.info("程序已启动，等待下次执行...")
        try:
            while True:
                schedule.run_pending()
                time.sleep(60)  # 每分钟检查一次
        except KeyboardInterrupt:
            self.logger.info("程序被用户中断")
        except Exception as e:
            self.logger.error(f"程序运行时发生错误: {e}")

def create_default_config_file(config_path: str = "config.json"):
    """创建默认配置文件"""
    config_manager = ConfigManager(config_path)
    config_manager.save_config()
    print(f"默认配置文件已创建: {config_path}")

def parse_arguments():
    """解析命令行参数"""
    import argparse
    
    parser = argparse.ArgumentParser(
        description="自动删除指定目录下过期CSV文件的程序",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
示例用法:
  %(prog)s                          # 使用默认配置文件 config.json
  %(prog)s --config myconfig.json   # 使用指定的配置文件
  %(prog)s --once                   # 只执行一次，不启动定时任务
  %(prog)s --dry-run                # 模拟运行模式
        """
    )
    
    parser.add_argument(
        '--config', '-c',
        default='config.json',
        help='配置文件路径 (默认: config.json)'
    )
    
    parser.add_argument(
        '--once', '-o',
        action='store_true',
        help='只执行一次，不启动定时任务'
    )
    
    parser.add_argument(
        '--dry-run', '-d',
        action='store_true',
        help='模拟运行模式，不会实际删除文件'
    )
    
    parser.add_argument(
        '--create-config',
        action='store_true',
        help='创建默认配置文件并退出'
    )
    
    parser.add_argument(
        '--target-directory', '-t',
        help='目标目录路径（覆盖配置文件中的设置）'
    )
    
    parser.add_argument(
        '--execution-time', '-e',
        help='执行时间 HH:MM 格式（覆盖配置文件中的设置）'
    )
    
    parser.add_argument(
        '--retention-days', '-r',
        type=int,
        help='保留天数（覆盖配置文件中的设置）'
    )
    
    return parser.parse_args()

def create_default_config_file(config_path: str = "config.json"):
    """创建默认配置文件"""
    config_manager = ConfigManager(config_path)
    config_manager.save_config()
    print(f"默认配置文件已创建: {config_path}")

def main():
    """
    主函数
    """
    print("=== 自动删除老CSV文件程序 ===")
    print("支持JSON配置文件配置")
    print()
    
    # 解析命令行参数
    args = parse_arguments()
    
    # 创建默认配置文件
    if args.create_config:
        create_default_config_file(args.config)
        return
    
    # 查找配置文件
    config_path = args.config
    if not os.path.exists(config_path):
        print(f"配置文件不存在: {config_path}")
        response = input("是否创建默认配置文件? (y/N): ").lower().strip()
        if response == 'y':
            create_default_config_file(config_path)
        else:
            print("程序退出")
            return
    
    # 加载配置
    print(f"加载配置文件: {config_path}")
    config_manager = ConfigManager(config_path)
    config = config_manager.get_config()
    
    # 覆盖配置（如果命令行参数指定）
    if args.target_directory:
        config['target_directory'] = args.target_directory
        print(f"命令行覆盖目录: {config['target_directory']}")
    
    if args.execution_time:
        if config_manager.validate_time_format(args.execution_time):
            config['execution_time'] = args.execution_time
            print(f"命令行覆盖执行时间: {config['execution_time']}")
        else:
            print(f"警告: 无效的时间格式 {args.execution_time}，保持原配置")
    
    if args.retention_days is not None:
        if args.retention_days >= 0:
            config['retention_days'] = args.retention_days
            print(f"命令行覆盖保留天数: {config['retention_days']}")
        else:
            print(f"警告: 保留天数必须 >= 0，保持原配置")
    
    if args.dry_run:
        config['dry_run'] = True
        print("启用模拟运行模式")
    
    # 显示配置信息
    print("当前配置:")
    print(f"  目标目录: {config['target_directory']}")
    print(f"  执行时间: {config['execution_time']}")
    print(f"  文件模式: {config['file_pattern']}")
    print(f"  保留天数: {config['retention_days']} 天")
    print(f"  模拟运行: {config['dry_run']}")
    print(f"  立即执行: {config['immediate_execution']}")
    print()
    
    # 设置日志
    setup_logging(config)
    logger = logging.getLogger(__name__)
    logger.info("程序启动")
    
    # 创建目录（如果不存在）
    target_dir = config['target_directory']
    if not os.path.exists(target_dir):
        logger.warning(f"目标目录不存在: {target_dir}")
        try:
            os.makedirs(target_dir, exist_ok=True)
            logger.info(f"已创建目录: {target_dir}")
        except Exception as e:
            logger.error(f"创建目录失败: {e}")
            print(f"错误: 无法创建目标目录 {target_dir}")
            return
    
    # 创建删除器实例
    deleter = OldFileDeleter(config)
    
    # 执行模式
    if args.once:
        print("执行一次文件删除操作...")
        deleter.delete_old_files()
        print("操作完成")
    else:
        # 询问是否立即执行（如果配置中未设置）
        if not config.get('immediate_execution', True):
            response = input("是否立即执行一次文件删除操作? (y/N): ").lower().strip()
            if response == 'y':
                deleter.delete_old_files()
        
        # 启动定时任务
        deleter.run_scheduler()

if __name__ == "__main__":
    main()